using Abp.Dependency;
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.ObjectMapping;
using Abp.Runtime.Session;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Core.Base.Reflection;
using Mt.Site.Core.ContentManagement.Entities;
using Mt.Site.Core.ContentManagement.Values;
using Mt.Site.Core.DiyContent.Form;
using Mt.Site.Core.DiyContent.Form.Entities;
using Mt.Site.Core.Storage;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;

namespace Mt.Site.Core.ContentManagement.Impl
{
    public class Content2FrontendManager : IContent2FrontendManager, ITransientDependency
    {
        private readonly IRepository<Content, Guid> _contentRepository;
        private readonly IRepository<Content2, Guid> _content2Repository;
        private readonly IRepository<ContentBody, long> _contentBodyRepository;
        private readonly IRepository<ContentField, long> _contentFieldRepository;
        private readonly IRepository<ContentMenu, Guid> _contentMenuRepository;
        private readonly IRepository<FileObject, Guid> _fileObjectRepository;

        private readonly IMenuContentHelper _menuContentHelper;
        public IObjectMapper ObjectMapper { get; set; }

        public IAbpSession AbpSession { get; set; }

        private readonly IUnitOfWorkManager _unitOfWorkManager;

        public Content2FrontendManager(
            IMenuContentHelper menuContentHelper,
            IRepository<Content, Guid> contentRepository,
            IRepository<Content2, Guid> content2Repository,
            IRepository<ContentBody, long> contentBodyRepository,
            IRepository<ContentField, long> contentFieldRepository,
            IRepository<ContentMenu, Guid> contentMenuRepository,
            IRepository<FileObject, Guid> fileObjectRepository,
            IUnitOfWorkManager unitOfWorkManager)
        {
            _contentRepository = contentRepository;
            _content2Repository = content2Repository;
            _contentBodyRepository = contentBodyRepository;
            _contentFieldRepository = contentFieldRepository;
            _contentMenuRepository = contentMenuRepository;
            _fileObjectRepository = fileObjectRepository;

            _menuContentHelper = menuContentHelper;
            _unitOfWorkManager = unitOfWorkManager;

            AbpSession = NullAbpSession.Instance;
            ObjectMapper = NullObjectMapper.Instance;
        }

        #region 泛型
        private async Task<Guid?> GetAuxiliaryIdAsync(long id)
        {
            var q = from c in _contentRepository.GetAll()
                    where c.AuxiliaryId == id
                    select c.Id;

            var result = await q.FirstOrDefaultAsync();
            if(result == Guid.Empty)
            {
                return null;
            }
            return result;
        }

        public async Task<T> GetContentByIdAsync<T>(long id, long menuId, long relateId) where T : new()
        {
            Guid? relateGuid = null;
            if(relateId > 0)
            {
                relateGuid = await GetAuxiliaryIdAsync(relateId);
            }

            return await GetContentByIdAsync<T>(id, menuId, relateGuid);
        }
        public async Task<T> GetContentByIdAsync<T>(long id, long menuId, Guid? relateId) where T : new()
        {
            var query = from cm in _contentMenuRepository.GetAll()
                        join c in _content2Repository.GetAll()
                        on cm.ContentId equals c.Id
                        where c.AuxiliaryId == id && c.IsActive && cm.MenuId == menuId && cm.RelateId == relateId
                        select c;
            var entity = await query.FirstOrDefaultAsync();
            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(Content), id);
            }

            var bodyFields = await _contentBodyRepository.GetAllListAsync(c => c.ContentId == entity.Id);
            var fields = await _contentFieldRepository.GetAllListAsync(c => c.ContentId == entity.Id);

            return GetItemByReflection<T>(entity, bodyFields, fields);
        }

        public async Task<T> GetContentByIdPreaViewAsync<T>(Guid id, long menuId, Guid? relateId) where T : new()
        {
            var query = from cm in _contentMenuRepository.GetAll()
                        join c in _content2Repository.GetAll()
                        on cm.ContentId equals c.Id
                        where c.Id == id  && cm.MenuId == menuId && cm.RelateId == relateId
                        select c;
            var entity = await query.FirstOrDefaultAsync();
            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(Content), id);
            }

            var bodyFields = await _contentBodyRepository.GetAllListAsync(c => c.ContentId == entity.Id);
            var fields = await _contentFieldRepository.GetAllListAsync(c => c.ContentId == entity.Id);

            return GetItemByReflection<T>(entity, bodyFields, fields);
        }


        public async Task<PagedContent<T>> GetContentListByPagedAsync<T>(
            Func<IQueryable<Content2>, IQueryable<Content2>> queryMethod,
            List<long> menuIdList,
            Guid? relateId,
            long? startTime,
            long? endTime,
            int skipCount, int getCount) where T : new()
        {
            var query = _content2Repository.GetAll().AsNoTracking().Where(t => t.TenantId == AbpSession.TenantId && t.Language == AbpSession.CultureName());
            query = queryMethod(query);
            IQueryable<Guid> q;

            if(menuIdList.Count  == 1)
            {
                var menuId = menuIdList[0];
                if (startTime != null)
                {
                    q = from cm in _contentMenuRepository.GetAll().AsNoTracking()
                        join c in query
                        on cm.ContentId equals c.Id
                        where cm.MenuId == menuId && cm.RelateId == relateId && cm.PublishTime >= startTime && cm.PublishTime < endTime
                        orderby cm.Order descending
                        select cm.ContentId;
                }
                else
                {
                    q = from cm in _contentMenuRepository.GetAll().AsNoTracking()
                        join c in query
                        on cm.ContentId equals c.Id
                        where cm.MenuId == menuId && cm.RelateId == relateId
                        orderby cm.Order descending
                        select cm.ContentId;
                }
            }
            else
            {   
                //多个栏目的数据混在一起，各自的排序号已经没有意义了，使用发布时间来排序
                if (startTime != null)
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    where menuIdList.Contains(cm.MenuId) && cm.RelateId == relateId && cm.PublishTime >= startTime && cm.PublishTime < endTime
                                    group cm by new { c.Id, c.PublishTime } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.PublishTime
                                    })
                        orderby qq.PublishTime descending
                        select qq.Id;
                }
                else
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    where menuIdList.Contains(cm.MenuId) && cm.RelateId == relateId
                                    group cm by new { c.Id, c.PublishTime } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.PublishTime
                                    })
                        orderby qq.PublishTime descending
                        select qq.Id;
                }
            }

            int count;
            List<Guid> list;

            using (_unitOfWorkManager.Current.DisableFilter(
                AbpDataFilters.MayHaveTenant,
                AbpDataFilters.MustHaveTenant,
                MtSiteConsts.AbpDataFilters.MustHaveLanguage))
            {
                count = await q.CountAsync();
                list = await q.Skip(skipCount).Take(getCount).ToListAsync();
            }

            var result = new List<T>();
            var contentList = await _content2Repository.GetAllListAsync(t => list.Contains(t.Id));
            foreach (var id in list)
            {
                var item = contentList.FirstOrDefault(t => t.Id == id);
                var item2 = GetItemByReflection<T>(item, null, null);
                result.Add(item2);
            }

            return new PagedContent<T>
            {
                TotolCount = count,
                Items = result,
                IsSingle = count < 2
            };
        }

        public async Task<List<T>> GetAllContentAsync<T>(
            Func<IQueryable<Content2>, IQueryable<Content2>> queryMethod, List<long> menuIdList, Guid? relateId, int maxCount, 
            bool shouldHasPic, List<string> aliasList, bool IsHomeRecommend) where T : new()
        {
            var q = _content2Repository.GetAll().AsNoTracking().Where(t => t.TenantId == AbpSession.TenantId && t.Language == AbpSession.CultureName());
            q = queryMethod(q);

            List<Guid> list;
            if (IsHomeRecommend)
            {
                var query1 = GetAllContent(q.Where(t => t.IsHomeRecommend), menuIdList, relateId, maxCount, shouldHasPic, aliasList);
                var query2 = GetAllContent(q.Where(t => !t.IsHomeRecommend), menuIdList, relateId, maxCount, shouldHasPic, aliasList);

                var q2 = query1.Concat(query2);

                //LogHelper.Logger.Debug(q2.ToQueryString());
                
                using (_unitOfWorkManager.Current.DisableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage))
                {
                    list = await q2.ToListAsync();
                }
            }
            else
            {
                var q2 =  GetAllContent(q, menuIdList, relateId, maxCount, shouldHasPic, aliasList);
                //LogHelper.Logger.Debug(q2.ToQueryString());
                
                using (_unitOfWorkManager.Current.DisableFilter(
                    AbpDataFilters.MayHaveTenant,
                    AbpDataFilters.MustHaveTenant,
                    MtSiteConsts.AbpDataFilters.MustHaveLanguage))
                {
                    list = await q2.ToListAsync();
                }
            }

            var contentList = await _content2Repository.GetAllListAsync(t => list.Contains(t.Id));
            var result = new List<T>();
            foreach (var id in list)
            {
                var item = contentList.FirstOrDefault(t => t.Id == id);
                var item2 = GetItemByReflection<T>(item, null, null);
                result.Add(item2);
            }
            return result;
        }


        private IQueryable<Guid> GetAllContent(
            IQueryable<Content2> query, List<long> menuIdList, Guid? relateId, int maxCount, bool shouldHasPic, List<string> aliasList)
        {
            IQueryable<Guid> q;

            if (menuIdList.Count == 1)
            {
                var menuId = menuIdList[0];
                if (shouldHasPic)
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    join f in _fileObjectRepository.GetAll().AsNoTracking()
                                    on cm.ContentId equals f.ContentId
                                    where cm.MenuId == menuId && cm.RelateId == relateId && aliasList.Contains(f.Alias) && f.IsActive
                                    group cm by new { c.Id, c.Order } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.Order
                                    })
                        orderby qq.Order descending
                        select qq.Id;

                }
                else
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    where cm.MenuId == menuId && cm.RelateId == relateId
                                    group cm by new { c.Id, c.Order } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.Order
                                    })
                        orderby qq.Order descending
                        select qq.Id;
                }
            }
            else
            {
                //多个栏目的数据混在一起，各自的排序号已经没有意义了，使用发布时间来排序
                if (shouldHasPic)
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    join f in _fileObjectRepository.GetAll().AsNoTracking()
                                    on cm.ContentId equals f.ContentId
                                    where menuIdList.Contains(cm.MenuId) && cm.RelateId == relateId && aliasList.Contains(f.Alias) && f.IsActive
                                    group cm by new { c.Id, c.PublishTime } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.PublishTime
                                    })
                        orderby qq.PublishTime descending
                        select qq.Id;

                }
                else
                {
                    q = from qq in (from cm in _contentMenuRepository.GetAll().AsNoTracking()
                                    join c in query
                                    on cm.ContentId equals c.Id
                                    where menuIdList.Contains(cm.MenuId) && cm.RelateId == relateId
                                    group cm by new { c.Id, c.PublishTime } into grouped
                                    select new
                                    {
                                        grouped.Key.Id,
                                        grouped.Key.PublishTime
                                    })
                        orderby qq.PublishTime descending
                        select qq.Id;
                }
            }

            return q.Take(maxCount);
        }

        private T GetItemByReflection<T>(Content2 content, List<ContentBody> bodyFields, List<ContentField> fields) where T : new()
        {
            var t = new T();
            var properties = TypeHelper.GetPrimitiveProperties<T>();
            foreach (var propertie in properties)
            {
                var propertieKey = propertie.Key;
                var value = TypeHelper.GetPropertyValue(content, propertieKey);
                if (value != null)
                {
                    TypeHelper.SetPropertyValue(t, propertieKey, value);
                }
                else
                {
                    ContentBody item = null;
                    if (bodyFields != null)
                    {
                        item = bodyFields.FirstOrDefault(c => c.FieldName == propertieKey);
                        if (item != null)
                        {
                            TypeHelper.SetPropertyValue(t, propertieKey, item.Value);
                        }
                    }
                    if (item == null && fields != null)
                    {
                        var item2 = fields.FirstOrDefault(c => c.FieldName == propertieKey);
                        if (item2 != null)
                        {
                            TypeHelper.SetPropertyValue(t, propertieKey, item2.Value);
                        }
                    }
                }
            }

            return t;
        }

        #endregion

        #region Object


        private async Task<ContentConfig> GetConfig(long menuId)
        {
            return await _menuContentHelper.GetConfig(null, menuId);
        }

        public async Task<Dictionary<string, object>> GetAsync(long id, long menuId, Guid? relateId)
        {
            var query = from cm in _contentMenuRepository.GetAll()
                        join c in _content2Repository.GetAll()
                        on cm.ContentId equals c.Id
                        where c.AuxiliaryId == id && c.IsActive && cm.MenuId == menuId && cm.RelateId == relateId
                        select c;
            var content = await query.FirstOrDefaultAsync();
            if (content == null)
            {
                throw new EntityNotFoundException(typeof(Content), id);
            }

            var config = await GetConfig(menuId);
            if (config == null)
            {
                throw new EntityNotFoundException(typeof(Content), id);
            }

            var result = new Dictionary<string, object>();
            var detailFields = config.Detail
                .Where(ContentFieldsHelper.CheckDataPredicate())
                .ToList();
            //依据配置返回
            var bodyFields = await _contentBodyRepository.GetAllListAsync(c => c.ContentId == content.Id);
            var fields = await _contentFieldRepository.GetAllListAsync(c => c.ContentId == content.Id);
            bool hasAdded;

            foreach (var propertie in detailFields)
            {
                hasAdded = false;
                var propertieKey = propertie.FieldName.ToPascalCase();
                var value = TypeHelper.GetPropertyValue(content, propertieKey);
                if (value != null)
                {
                    hasAdded = true;
                    result.Add(propertieKey, value);
                }
                else
                {
                    ContentBody item = null;
                    if (bodyFields != null)
                    {
                        item = bodyFields.FirstOrDefault(c => c.FieldName == propertieKey);
                        if (item != null)
                        {
                            hasAdded = true;
                            result.Add(propertieKey, item.Value);
                        }
                    }
                    if (item == null && fields != null)
                    {
                        var item2 = fields.FirstOrDefault(c => c.FieldName == propertieKey);
                        if (item2 != null)
                        {
                            hasAdded = true;
                            result.Add(propertieKey, item2.Value);
                        }
                    }
                }
                if (!hasAdded) //正常不会出现
                {
                    var defaultValue = TypeHelper.GetDefaultValue(propertie.FieldType, propertie.DefaultValue);
                    result[propertieKey] = defaultValue;
                }
            }

            foreach (var propertieKey in Content2Manager._specialFields)
            {
                var value = TypeHelper.GetPropertyValue(content, propertieKey);
                result[propertieKey] = value;
            }

            return result;
        }

        #endregion

        public async Task<int> UpdatePv(long id)
        {
            var entity = await _content2Repository.FirstOrDefaultAsync(c => c.AuxiliaryId == id);
            if (entity != null)
            {
                entity.Hit += 1;
                return entity.Hit;
            }
            return 1;
        }

        public async Task<PreAndNextItemId> GetPreAndNextLink(long menuId, Guid? relateId, Guid id)
        {
            // TODO: 调整方法
            var query2 = from cm in _contentMenuRepository.GetAll().AsNoTracking()
                         join c in _content2Repository.GetAll().AsNoTracking()
                         on cm.ContentId equals c.Id
                         where c.IsActive && cm.MenuId == menuId && cm.RelateId == relateId
                         orderby cm.Order descending, cm.PublishTime descending
                         select new { c.Id, c.AuxiliaryId };

            var idList = await query2.ToListAsync();

            var result = new PreAndNextItemId();

            var index = idList.FindIndex(t => t.Id == id);
            if (index >= 0)
            {
                if (index < idList.Count - 1)
                {
                    //Next
                    result.NextId = idList[index + 1].Id;
                    result.NextAuxiliaryId = idList[index + 1].AuxiliaryId;
                }
                if (index > 0)
                {
                    //Pre
                    result.PreId = idList[index - 1].Id;
                    result.PreAuxiliaryId = idList[index - 1].AuxiliaryId;
                }
            }
            return result;
        }
        public async Task<Dictionary<Guid, List<ContentMenuDto>>> GetMenuIdsByContentIdListAsync(List<long> menuIdList, List<Guid> idList)
        {
            var query = from cm in _contentMenuRepository.GetAll().AsNoTracking()
                        where menuIdList.Contains(cm.MenuId) && idList.Contains(cm.ContentId)
                        select cm;
            var list = await query.ToListAsync();

            return ObjectMapper.Map<List<ContentMenuDto>>(list)
                .GroupBy(t => t.ContentId)
                .ToDictionary(t => t.Key, t => t.ToList());
        }
    }
}
